﻿#region Using directives

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Drawing.Text;

#endregion

namespace MetaHeuristics.Tsp
{
    public class TspRenderer
    {
        #region Fields
        private TspProblem tsp;

        private Color backgroundColor = Color.White;
        private bool routesEnabled = true;
        private Color routeColor = Color.LightGray;
        private float routeWidth = 1;
        private bool citiesEnabled = true;
        private Color cityColor = Color.Black;
        private float cityRadius = 2;
        private bool citiesNameEnabled = true;
        private Color cityNameColor = Color.Black;
        private Font cityNameFont = new Font("Verdana", 8.25f);

        private float scale = 1;
        #endregion

        public TspRenderer(TspProblem tsp)
        {
            if (tsp == null)
                throw new ArgumentNullException("tsp");
            this.tsp = tsp;
        }

        #region Properties
        [Browsable(false)]
        public TspProblem Tsp
        {
            get
            {
                return tsp;
            }
        }

        [Category("Appearance")]
        public Color BackgroundColor
        {
            get
            {
                return backgroundColor;
            }

            set
            {
                backgroundColor = value;
            }
        }

        [Category("Appearance")]
        public bool RoutesEnabled
        {
            get
            {
                return routesEnabled;
            }

            set
            {
                routesEnabled = value;
            }
        }

        [Category("Appearance")]
        public Color RouteColor
        {
            get
            {
                return routeColor;
            }

            set
            {
                routeColor = value;
            }
        }

        [Category("Appearance")]
        public float RouteWidth
        {
            get
            {
                return routeWidth;
            }

            set
            {
                routeWidth = value;
            }
        }

        [Category("Appearance")]
        public bool CitiesEnabled
        {
            get
            {
                return citiesEnabled;
            }

            set
            {
                citiesEnabled = value;
            }
        }

        [Category("Appearance")]
        public Color CityColor
        {
            get
            {
                return cityColor;
            }

            set
            {
                cityColor = value;
            }
        }

        [Category("Appearance")]
        public float CityRadius
        {
            get
            {
                return cityRadius;
            }

            set
            {
                cityRadius = value;
            }
        }


        [Category("Appearance")]
        public bool CitiesNameEnabled
        {
            get
            {
                return citiesNameEnabled;
            }

            set
            {
                citiesNameEnabled = value;
            }
        }

        [Category("Appearance")]
        public Color CityNameColor
        {
            get
            {
                return cityNameColor;
            }

            set
            {
                cityNameColor = value;
            }
        }

        [Category("Appearance")]
        public Font CityNameFont
        {
            get
            {
                return cityNameFont;
            }

            set
            {
                cityNameFont = value;
            }
        } 
        #endregion

        #region Render methods
        public virtual void Render(string fileName, int width, int height)
        {
            this.Render(fileName, width, height, ImageFormat.Png);
        }

        public virtual void Render(string fileName, int width, int height, ImageFormat format)
        {
            if (fileName == null)
                throw new ArgumentNullException("fileName");
            using (Bitmap bmp = new Bitmap(width, height))
            {
                Render(bmp);
                string name = fileName;
                if (!name.EndsWith(format.ToString().ToLower()))
                    name+="." + format.ToString().ToLower();
                bmp.Save(name, format);
            }
        }
        public virtual void Render(Image img)
        {
            if (img == null)
                throw new ArgumentNullException("img");
            using (Graphics g = Graphics.FromImage(img))
            {
                g.SmoothingMode = SmoothingMode.HighQuality;
                g.TextRenderingHint = TextRenderingHint.AntiAlias;
                Render(g);
            }
        }

        public virtual void Render(Graphics g)
        {
            if (g == null)
                throw new ArgumentNullException("g");

            // set bounds
            GraphicsState gstate = null;
            try
            {
                this.RenderBackground(g);

                gstate = g.Save();
                this.GetScalingFactor(g);

                if (this.RoutesEnabled)
                    this.RenderRoutes(g);

               if (this.CitiesEnabled)
                    this.RenderCities(g);

                // render optional
                this.CustomRender(g);
            }
            finally
            {
                if (gstate != null)
                    g.Restore(gstate);
            }
        }
        #endregion

        #region Scaling and coordinates
        protected virtual void GetScalingFactor(Graphics g)
        {
            float widthRatio = g.VisibleClipBounds.Width / this.Tsp.CityBounds.Width;
            float heightRatio = g.VisibleClipBounds.Height / this.Tsp.CityBounds.Height;

            if (widthRatio < heightRatio)
                this.scale=widthRatio*0.95f;
            else
                this.scale=heightRatio*0.95f;
        }

        protected float ProjectX(float x)
        {
            return this.scale*0.05f + (x - this.Tsp.CityBounds.X) * this.scale;
        }
        protected float ProjectY(float y)
        {
            return this.scale * 0.05f + (this.Tsp.CityBounds.Top - y) * this.scale;
        }
        protected PointF Project(PointF p)
        {
            PointF pp = new PointF(ProjectX(p.X), ProjectY(p.Y));
            return pp;
        }
        #endregion

        #region Protected virtual methods
        protected virtual void CustomRender(Graphics g)
        { }

        protected virtual void RenderBackground(Graphics g)
        {
            g.Clear(this.BackgroundColor);
        }

        protected virtual void RenderRoutes(Graphics g)
        {
            for (int i = 0; i < this.Tsp.Cities.Count; ++i)
            {
                for (int j = i + 1; j < this.Tsp.Cities.Count; ++j)
                {
                    this.RenderRoute(g, i, j);
                }
            }
        }

        protected virtual void RenderRoute(Graphics g, int i, int j)
        {
            City source = this.Tsp.Cities[i];
            City target = this.Tsp.Cities[j];
            g.DrawLine(CreateRoutePen(g,i,j), Project(source.Position), Project(target.Position));
        }

        protected virtual void RenderCities(Graphics g)
        {
            for (int i = 0; i < this.Tsp.Cities.Count; ++i)
            {
                this.RenderCity(g, i);
                if (this.CitiesNameEnabled)
                    this.RenderCityName(g, i);
            }
        }

        protected virtual void RenderCity(Graphics g, int i)
        {
            City city = this.Tsp.Cities[i];
            RectangleF rect = new RectangleF(
                ProjectX(city.Position.X)-this.CityRadius,
                ProjectY(city.Position.Y)-this.CityRadius,
                this.CityRadius*2,
                this.CityRadius*2
                );

            g.FillEllipse(CreateCityBrush(g,i), rect);
        }

        protected virtual void RenderCityName(Graphics g, int i)
        { 
            City city = this.Tsp.Cities[i];
            g.DrawString(
                city.Name,
                this.CityNameFont,
                this.CreateCityNameBrush(g,i),
                Project(city.Position)
                );
        }
        #endregion

        #region Brushes and pens
        protected virtual Brush CreateBackgroundBrush(Graphics g)
        {
            return new SolidBrush(this.BackgroundColor);
        }

        protected virtual Brush CreateCityBrush(Graphics g, int i)
        {
            return new SolidBrush(this.CityColor);
        }

        protected virtual Pen CreateRoutePen(Graphics g, int i, int j)
        {
            return new Pen(this.RouteColor, this.RouteWidth);
        }

        protected virtual Brush CreateCityNameBrush(Graphics g, int i)
        {
            return new SolidBrush(this.CityNameColor);
        }
        #endregion
    }
}
